﻿namespace LuckyAssetManagerContrib.Processors
{
	using System;
	using System.Collections.Generic;
	using System.Globalization;
	using System.IO;
	using System.Linq;
	using System.Runtime.Caching;
	using System.Web;

	using Lucky.AssetManager.Assets;
	using Lucky.AssetManager.Assets.AssetReaders;
	using Lucky.AssetManager.Processors;

	using Configuration;
	using Resources;

	/// <summary>
	/// Processor, responsible for choosing appropriate version 
	/// of asset file, depending on current mode of web application 
	/// (debug mode - debug versions of asset files; 
	/// release mode - minified versions)
	/// </summary>
	public sealed class SelectPathProcessor : IProcessor
	{
		/// <summary>
		/// Memory cache
		/// </summary>
		private readonly ObjectCache _memCache;

		/// <summary>
		/// Configuration settings of asset processor
		/// </summary>
		private readonly SelectPathProcessorElement _config;

		/// <summary>
		/// Flag that web application is in debug mode
		/// </summary>
		private readonly bool _isDebugMode;

		/// <summary>
		/// Synchronizer of requests to server cache
		/// </summary>
		private static readonly object _cacheSynchronizer = new object();

		/// <summary>
		/// Extensions of JS-files for debug mode
		/// </summary>
		private static readonly string[] _debugJsExtensions = new[] { ".debug.js", ".js", ".min.js" };

		/// <summary>
		/// Extensions of JS-files for release mode
		/// </summary>
		private static readonly string[] _releaseJsExtensions = new[] { ".min.js", ".js", ".debug.js" };

		/// <summary>
		/// Extensions of CSS-files for debug mode
		/// </summary>
		private static readonly string[] _debugCssExtensions = new[] { ".css", ".min.css" };

		/// <summary>
		/// Extensions of CSS-files for release mode
		/// </summary>
		private static readonly string[] _releaseCssExtensions = new[] { ".min.css", ".css" };

		/// <summary>
		/// Gets or sets culture info
		/// </summary>
		public CultureInfo CultureInfo
		{
			get;
			set; 
		}


		public SelectPathProcessor()
			: this(AssetManagerContext.Configuration.CacheFactory.GetCache(), 
				AssetManagerContext.ContribConfiguration.SelectPathProcessor,
				HttpContext.Current.IsDebuggingEnabled)
		{ }

		public SelectPathProcessor(ObjectCache memCache, SelectPathProcessorElement config, 
			bool isDebugMode)
		{
			_memCache = memCache;
			_config = config;
			_isDebugMode = isDebugMode;
		}


		/// <summary>
		/// Chooses appropriate versions of files, depending on current 
		/// mode of web application 
		/// </summary>
		/// <param name="assets">Set of assets</param>
		/// <returns>Set of assets adapted for current mode of web application</returns>
		public IEnumerable<IAsset> Process(IEnumerable<IAsset> assets)
		{
			if (assets == null)
			{
				throw new ArgumentException(Strings.Common_ValueIsNull, "assets");
			}

			List<IAsset> assetList = assets.ToList();
			if (assetList.Count == 0)
			{
				return assetList;
			}

			List<IAsset> result = assetList
				.Where(a => !a.IsProcessable)
				.ToList()
				;

			foreach (IAsset asset in assetList
				.Where(a => a.IsProcessable))
			{
				if (Utils.IsJavaScriptAsset(asset) || Utils.IsCssAsset(asset))
				{
					// Set value of asset file path that corresponds to 
					// current mode of web application
					asset.Path = GetAppropriateAssetFilePath(asset.Path);
					asset.Reader = new FileSystemAssetReader(asset);
				}

				result.Add(asset);
			}

			return result;
		}

		/// <summary>
		/// Gets version of file path, most appropriate for 
		/// current mode of web application
		/// </summary>
		/// <param name="filePath">Asset file path</param>
		/// <returns>Path to file, corresponding current mode 
		/// of web application</returns>
		private string GetAppropriateAssetFilePath(string filePath)
		{
			string appropriateFilePath = filePath.Trim();

			if (appropriateFilePath.Length > 0)
			{
				// Asset file path without additional extension
				string filePathWithoutAdditionalExtension;

				// List of file extensions, sorted in order 
				// of relevance to current mode of web application
				string[] appropriateFileExtensions;
				if (Utils.IsJavaScriptAsset(appropriateFilePath))
				{
					appropriateFileExtensions = _isDebugMode ? 
						_debugJsExtensions : _releaseJsExtensions;
					filePathWithoutAdditionalExtension = 
						Utils.RemoveAdditionalJsFileExtension(appropriateFilePath);
				}
				else if (Utils.IsCssAsset(appropriateFilePath))
				{
					appropriateFileExtensions = _isDebugMode ? 
						_debugCssExtensions : _releaseCssExtensions;
					filePathWithoutAdditionalExtension = 
						Utils.RemoveAdditionalCssFileExtension(appropriateFilePath);
				}
				else
				{
					return appropriateFilePath;
				}

				lock (_cacheSynchronizer)
				{
					// Trying to get file path to appropriate version of asset from cache
					string cacheItemKey = String.Format(
						Constants.AppropriateAssetFilePathCacheItemKeyPattern,
						filePath.ToLower().GetHashCode().ToString(CultureInfo.InvariantCulture));
					object memCacheItem = _memCache.Get(cacheItemKey);

					if (memCacheItem != null)
					{
						appropriateFilePath = (string) memCacheItem;
					}
					else
					{
						// If cache item is empty, then get appropriate version of asset 
						// file path based on list of file extensions
						appropriateFilePath = ProbeAssetFilePath(filePathWithoutAdditionalExtension, 
							appropriateFileExtensions);

						// Save appropriate asset file path in cache
						var policy = new CacheItemPolicy();
						if (_config.UseMemoryCacheSlidingExpiration)
						{
							policy.SlidingExpiration = TimeSpan.FromMinutes(_config.MemoryCacheDurationInMinutes);
						}
						else
						{
							policy.AbsoluteExpiration = DateTime.Now.AddMinutes(_config.MemoryCacheDurationInMinutes);
						}

						_memCache.Add(cacheItemKey, appropriateFilePath, policy);
					}
				}
			}

			return appropriateFilePath;
		}

		/// <summary>
		/// Gets appropriate version of asset file path based 
		/// on list of file extensions
		/// </summary>
		/// <param name="filePath">Asset file path</param>
		/// <param name="extensions">List of file extensions</param>
		/// <returns>Asset file path with modified extension</returns>
		private static string ProbeAssetFilePath(string filePath, string[] extensions)
		{
			string changedFilePath = String.Empty;

			foreach (string extension in extensions)
			{
				changedFilePath = Path.ChangeExtension(filePath, extension);
				if (Utils.CheckFileExist(changedFilePath))
				{
					return changedFilePath;
				}
			}

			throw new FileNotFoundException(String.Format(Strings.Common_FileNotExist, changedFilePath));
		}
	}
}